package cn.itsource.pethome.base.service.impl;

import cn.itsource.pethome.base.domain.dto.BinderDTO;
import cn.itsource.pethome.base.domain.dto.LoginDTO;
import cn.itsource.pethome.base.domain.dto.WechatDTO;
import cn.itsource.pethome.base.service.ILoginService;
import cn.itsource.pethome.constants.BaseConstants;
import cn.itsource.pethome.jwt.JwtUtils;
import cn.itsource.pethome.jwt.PayloadData;
import cn.itsource.pethome.org.domain.Employee;
import cn.itsource.pethome.org.mapper.EmployeeMapper;
import cn.itsource.pethome.system.domain.Menu;
import cn.itsource.pethome.system.mapper.MenuMapper;
import cn.itsource.pethome.user.domain.Logininfo;
import cn.itsource.pethome.user.domain.User;
import cn.itsource.pethome.user.domain.Wxuser;
import cn.itsource.pethome.user.mapper.LogininfoMapper;
import cn.itsource.pethome.user.mapper.UserMapper;
import cn.itsource.pethome.user.mapper.WxuserMapper;
import cn.itsource.pethome.user.service.IUserService;
import cn.itsource.pethome.util.*;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.security.PrivateKey;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class LoginServiceImpl implements ILoginService {
    @Autowired
    private LogininfoMapper logininfoMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private WxuserMapper wxuserMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private IUserService iUserService;
    @Autowired
    private EmployeeMapper employeeMapper;
    @Autowired
    private MenuMapper menuMapper;

    @Value("${jwt.rsa.pri}")
    private String pri;

    @Transactional
    @Override
    public Map<String,Object> account(LoginDTO loginDTO) {
        //判断数据是否为null
       if(StringUtils.isEmpty(loginDTO.getPassword())){
           throw new BusinessException("密码不能为空");
       }
       if(StringUtils.isEmpty(loginDTO.getUsername())){
           throw new BusinessException("用户名不能为空");
       }
        if(StringUtils.isEmpty(loginDTO.getType())){
            throw new BusinessException("类型不能为空");//你自己想
        }
       //
       //校验当前用户是否存在
       Logininfo logininfo = logininfoMapper.queryUsernameORPhoneOREmail(loginDTO);
       if(logininfo == null){
           throw new BusinessException("账号或者密码错误");
       }
       //进行密码的校验
        String salt = logininfo.getSalt();
        String saltPassword = MD5Utils.encrypByMd5(loginDTO.getPassword() + salt);
        if(!logininfo.getPassword().equals(saltPassword)){
            throw new BusinessException("账号或者密码错误");
        }
//        return resultToken(logininfo);
        return resultJWTToken(logininfo);
    }

    /**
     * 生成token 并且返回 结果集 包含了token 和 userinfo
     * @param logininfo
     * @return
     */
    private Map<String,Object> resultToken(Logininfo logininfo){
        //生成token   每一个人生成的token必须唯一
        String token = UUID.randomUUID().toString();//请注意
        //key 是token  value 是账号数据 过期时间30分钟
        redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
        Map<String,Object> map = new HashMap();
        map.put("token",token);
        //很严重的问题 不能将盐值和密码暴露出去
        logininfo.setSalt("");
        logininfo.setPassword("");
        map.put("userinfo",logininfo);

        //todo 菜单的信息
        //todo 权限的信息
        return map;
    }

    /**
     * 返回JWT Token
     * @param logininfo
     * @return
     */
    private Map<String,Object> resultJWTToken(Logininfo logininfo){
        Map<String,Object> map = new HashMap();
        try {
            //1.得到私钥
            PrivateKey privateKey = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader().getResource(pri).getFile());
            //2.得到token  设置过期时间
            //我们要保存几个信息
            //1.employee
            //2.user
            //3.menu
            //4.permission
            PayloadData payloadData = new PayloadData();
            if(logininfo.getType() == 1){
                User user = userMapper.queryOneByLogininfoId(logininfo.getId());
                payloadData.setUser(user);
            }else {
                Employee employee = employeeMapper.queryOneByLogininfoId(logininfo.getId());
                payloadData.setAdmin(employee);
                List<String> sns = employeeMapper.selectSn(logininfo.getId());
                payloadData.setPermissions(new HashSet<>(sns));//new HashSet<>(sns) 将list转set集合
                //获取自己有的所有菜单 但是可能会出现没有父级菜单的情况
                List<Menu> menus =  employeeMapper.selectMenuTreeByLogininfoId(logininfo.getId());
                //获取菜单的所有父id
                List<Long> parentids = menus.stream()
                        .map(Menu::getParentId)//只获取ParentId
                        .distinct()//去重
                        .collect(Collectors.toList());//转为list
                //查询所有的父级目录
                List<Menu> pMenus =  menuMapper.queryMenusByPIds(parentids);
                //先将子集装进去
                HashSet<Menu> menus1 = new HashSet<>(menus);
                //在将父级装进去
                menus1.addAll(pMenus);
                payloadData.setMenus(getTree(menus1));
            }
            String token = JwtUtils.generateTokenExpireInMinutes(payloadData, privateKey, 3000);
            //3.返回
            map.put("token",token);
            map.put("menu",payloadData.getMenus());
            map.put("permissions",payloadData.getPermissions());
            //本项目 目前 前端 不会去解析jwt
            if(payloadData.getUser()==null){
                map.put("userinfo",payloadData.getAdmin());
            }else {
                map.put("userinfo",payloadData.getUser());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    //无限级树
    public Set<Menu> getTree(Set<Menu> set){
        Set<Menu> tree = new HashSet<>();
        for (Menu menu : set) {
            if(menu.getParentId() == null){
                tree.add(menu);
            }else {
                for (Menu menu1 : set) {
                    if(menu.getParentId() == menu1.getId()){
                        menu1.getChildren().add(menu);
                    }
                }
            }
        }
        return tree;
    }

    /**
     *  1.验证code是否为null
     *       2.发送http请求 获取access_token 和 openid
     *          2.1 ---- 有可能微信出问题  (可以不用做)
     *       3.根据 openid 去查询数据库
     *         3.1 有
     *            一 openid 去查询是user===> 我们的user里面有logininfo的id查询 ===> logininfo
     *            二 将 logininfo 返回回去  map来装
     *            三 直接生成token  保存redis  返回数据给前端
     *         3.2 没有
     *            返回false 设置msg  = NoBinder --- 利用后置拦截器 跳转页面
     *            access_token 和 openId 也要返回回去
     * @param wechatDTO
     * @return
     */
    @Override
    public AjaxResult wechat(WechatDTO wechatDTO) {//微信登录

        if(StringUtils.isEmpty(wechatDTO.getCode())//参数校验
                ||StringUtils.isEmpty(wechatDTO.getBinderUrl())){
            throw new BusinessException("非法登录");
        }
        //去获取 access_token 和 openId   替换里面的值
        String url = BaseConstants.WxConstants.GETACCESS_TOKEN_URL
                .replace("APPID",BaseConstants.WxConstants.APPID)
                .replace("SECRET",BaseConstants.WxConstants.SECRET)
                .replace("CODE",wechatDTO.getCode());
        String result = HttpClientUtils.httpGet(url);
        JSONObject jsonObject = JSONObject.parseObject(result);//将json字符串转为json对象
        //得到access_token 和 openid
        String access_token = jsonObject.getString("access_token");
        String openid = jsonObject.getString("openid");
        //根据openid去查询数据 看有没有绑定的
        Wxuser wxuser = wxuserMapper.queryByOpenId(openid);
        if(wxuser==null){
            //拼接 绑定页面的链接
            String data =wechatDTO.getBinderUrl()+"?"+"access_token="+access_token+"&openid="+openid;
            return AjaxResult.me().setSuccess(false).setMsg("NoBinder").setData(data);
        }
        //完成微信登录
        Long userId = wxuser.getUserId();
        User user = userMapper.queryOne(userId);
        Long logininfoId = user.getLogininfoId();
        Logininfo logininfo = logininfoMapper.queryOne(logininfoId);
//        Map<String, Object> stringObjectMap = resultToken(logininfo);
        return AjaxResult.me().setData(  resultJWTToken(logininfo));
    }

    /**
     * 1.参数校验  手机号  验证码  access_token openid
     *       1.1redis的验证码校验
     *       2.根据手机号 去查询数据库  只能查询t_user表  不能查询logininfo
     *         2.1 有
     *            一 获取微信用户信
     *            二 直接获取userid设置到wxuser里面去
     *            三 保存t_wxuser
     *         2.2 无
     *            自己去构建一个user对象
     *            然后保存user对象
     *            获取微信用户信
     *            直接获取userid设置到wxuser里面去
     *            保存t_wxuser
     *       3.生成token
     *       4.查询logininfo出来
     *       5.将参数返回
     * @param binderDTO
     * @return
     */
    @Override
    public AjaxResult binder(BinderDTO binderDTO) {
        //验证
        if(StringUtils.isEmpty(binderDTO.getPhone())
                ||StringUtils.isEmpty(binderDTO.getAccess_token())
                ||StringUtils.isEmpty(binderDTO.getVerifyCode())
                ||StringUtils.isEmpty(binderDTO.getOpenid())
        ){
            throw new BusinessException("非法请求");
        }
        //校验验证码
        String key = BaseConstants.register.BINDER+binderDTO.getPhone();
        Object code = redisTemplate.opsForValue().get(key);
        if(code == null){
            throw new BusinessException("验证码已失效");
        }
        if(!code.toString().split(":")[0].equalsIgnoreCase(binderDTO.getVerifyCode())){
            throw new BusinessException("验证码输入错误");
        }

        //通过 access_token 和  openid 去获取用户信息
        String url = BaseConstants.WxConstants.GETWXUSER_URL
                .replace("ACCESS_TOKEN",binderDTO.getAccess_token())
                .replace("OPENID",binderDTO.getOpenid());
        //获取微信的用户信息
        String result = HttpClientUtils.httpGet(url);
        Wxuser wxuser = JSONObject.parseObject(result, Wxuser.class);
        //根据手机号去查询用户  之前注册了用户 但是没有绑定微信
        User user = userMapper.queryByPhone(binderDTO.getPhone());
        if(user == null){
            user = new User();
            user.setPassword("111111");//后台自动生成
            user.setPhone(binderDTO.getPhone());
            user.setUsername(binderDTO.getPhone());
            iUserService.insert(user);
            //发送短信
        }
        wxuser.setUserId(user.getId());
        wxuserMapper.insert(wxuser);
        //获取登录信息
        Logininfo logininfo = logininfoMapper.queryOne(user.getLogininfoId());
        //生成token
//        Map<String, Object> stringObjectMap = resultToken(logininfo);
        return AjaxResult.me().setData(resultJWTToken(logininfo));
    }
}
